{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Resourcing: Solar panel cells\n", "\n", "## Try me\n", " [![Open In Colab](../../_static/colabs_badge.png)](https://colab.research.google.com/github/ffraile/operations-research-notebooks/blob/main/docs/source/CLP/solved/Manufacturing%20solar%20cell%20panels%20(Solved%20CBC).ipynb)[![Binder](../../_static/binder_badge.png)](https://mybinder.org/v2/gh/ffraile/operations-research-notebooks/main?labpath=docs%2Fsource%2FCLP%2Fsolved%2FManufacturing%20solar%20cell%20panels%20(Solved%20CBC).ipynb)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem Definition\n", "\n", "Weyland Corp is a British firm that manufactures solar panel cells. This firm supplies three differnt types of cell called: fast, normal and ultra. There are three operations involved in the manufacturing process. The following table describes the hours/month required to manufacture each model: \n", "\n", "**Table 1:** hours/month requirements\n", "\n", "| Operation| Fast (hours/month) | Normal (hours/month) | Ultra (hours/month)| \n", "|----------|----------------------|------------------------|---------------------|\n", "| 1 | 1 | 3 | 2 |\n", "| 2 | 2 | 0 | 3 |\n", "| 3 | 1 | 4 | 0 |\n", "\n", "And the following table describes the total hours available for each operation:\n", "\n", "**Table 2:** Total hours/month available\n", "\n", "| Operation | Hours/Month |\n", "| ----------| ------------|\n", "| 1 | 400 |\n", "| 2 | 600 |\n", "| 3 | 600 |\n", "\n", "The profit per each model is specified in the following table:\n", "**Table 3:** Unit of profit per model\n", "\n", "| Model | Profit/Unit |\n", "| ----------| ------------|\n", "| Normal | 30 |\n", "| Economic | 20 |\n", "| Luxury | 40 |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem Model\n", "### Decision variables\n", "The decision variables are: \n", "\n", "$x_1:$Units of Fast cells / month\n", "\n", "$x_2:$Units of Normal cells / month\n", "\n", "$x_3:$Units of Ultra cells / month\n", "\n", "### Objective Function\n", "The objective function is:\n", "\n", "$\\max z=30x_1+20x_2+40x_3$\n", "\n", "### Constraints\n", "Subject to the following constraints:\n", "\n", "$x_1+3x_2+2x_3 \\leq 400$\n", "\n", "$2x_1+0x_2+3x_3 \\leq 600$\n", "\n", "$x_1+4x_2+0x_3 \\leq 600$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem Solution with Pulp\n", "Let us solve the problem with pulp. We start by importing PULP and instantiating the problem:" ] }, { "cell_type": "code", "execution_count": 1, "outputs": [ { "name": "stdout", "text": [ "Requirement already satisfied: pandas in c:\\users\\franc\\anaconda3\\lib\\site-packages (1.3.5)\n", "Requirement already satisfied: pytz>=2017.3 in c:\\users\\franc\\anaconda3\\lib\\site-packages (from pandas) (2019.3)\n", "Requirement already satisfied: numpy>=1.17.3; platform_machine != \"aarch64\" and platform_machine != \"arm64\" and python_version < \"3.10\" in c:\\users\\franc\\anaconda3\\lib\\site-packages (from pandas) (1.21.5)\n", "Requirement already satisfied: python-dateutil>=2.7.3 in c:\\users\\franc\\anaconda3\\lib\\site-packages (from pandas) (2.8.0)\n", "Requirement already satisfied: six>=1.5 in c:\\users\\franc\\anaconda3\\lib\\site-packages (from python-dateutil>=2.7.3->pandas) (1.12.0)\n", "Requirement already satisfied: numpy in c:\\users\\franc\\anaconda3\\lib\\site-packages (1.21.5)\n", "Requirement already satisfied: pulp in c:\\users\\franc\\anaconda3\\lib\\site-packages (1.6.8)\n", "Requirement already satisfied: pyparsing>=2.0.1 in c:\\users\\franc\\anaconda3\\lib\\site-packages (from pulp) (2.4.2)\n" ], "output_type": "stream" } ], "source": [ "!pip install pandas\n", "!pip install numpy\n", "!pip install pulp " ], "metadata": { "collapsed": false, "pycharm": { "name": "#%%\n", "is_executing": false } } }, { "cell_type": "code", "execution_count": 2, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "from IPython.display import display, Markdown\n", "import pulp" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "# Instantiate our problem class\n", "model = pulp.LpProblem(\"Maximising profits for Weyland Corp\", pulp.LpMaximize)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this problem, we have three decision variables. We could name them individually like in the first example, but this does not scale (imagine we had hundreds). Instead we will use lists and tuple lists which can be really useful to minimise the problem configuration. \n", "\n", "First we create the lists:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "# Construct our decision variable lists\n", "cell_types = ['fast', 'normal', 'ultra']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The decision variables have the same characteristics (lower bound of 0, continuous variables). We can use the LpVariable object's dict functionality to create the variables from the tuples. The good thing about tuples is that we could add more dimensions (eg cell categories) and still create all decision variables in one line of code." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "cell_units = pulp.LpVariable.dicts(\"cells\",\n", " (i for i in cell_types),\n", " lowBound=0,\n", " cat='Continuous')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "PuLP provides an lpSum vector calculation for the sum of a list of linear expressions. Although we only have 6 decision variables, we will use lpSum to construct the expression using this feature, again for the sake of scalibility.\n" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "# Technological Coefficients:\n", "unit_profits = [30, 20, 40]\n", "# Objective Function\n", "\n", "model += (\n", " pulp.lpSum([\n", " unit_profits[i] * cell_units[cell_types[i]]\n", " for i in range(len(cell_types))])\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we add the constraints using the tuplets to identify the coefficients" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [], "source": [ "# Operations \n", "operations = [\"Operation 1\", \"Operation 2\", \"Operation 3\"]\n", "# Constraints\n", "A=[[1, 3, 2], #Coefficients of the first constraint\n", " [2, 0, 3], #Coefficients of the second constraint\n", " [1, 4, 0]] #Coefficients of the third constraint\n", "\n", "# Capacities\n", "b = [400, 600, 600]\n", " #Iterate matrix raws\n", "for i in range(len(A)):\n", " model += pulp.lpSum([\n", " A[i][j] * cell_units[cell_types[j]] \n", " for j in range(len(cell_types))]) <= b[i] , operations[i] \n", "#note that in this case all constraints are of type less or equal" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [ { "data": { "text/plain": "'Optimal'" }, "metadata": {}, "output_type": "execute_result", "execution_count": 8 } ], "source": [ "# Solve our problem\n", "model.solve()\n", "\n", "pulp.LpStatus[model.status]" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "pycharm": { "is_executing": false } }, "outputs": [ { "data": { "text/plain": "", "text/markdown": "Total profit is 9666.67 €" }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": "", "text/markdown": "The following table shows the decision variables: " }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": " Variables Solution Reduced cost\nfast cells_fast 300.000000 -0.000000\nnormal cells_normal 33.333333 -0.000000\nultra cells_ultra 0.000000 -8.333333", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
VariablesSolutionReduced cost
fastcells_fast300.000000-0.000000
normalcells_normal33.333333-0.000000
ultracells_ultra0.000000-8.333333
\n
" }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": "", "text/markdown": "The following table shows the constraints: " }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": " Constraint Right Hand Side Slack Shadow Price\n0 Operation_1 400 -0.00000 6.666667\n1 Operation_2 600 -0.00000 11.666667\n2 Operation_3 600 166.66667 -0.000000", "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
ConstraintRight Hand SideSlackShadow Price
0Operation_1400-0.000006.666667
1Operation_2600-0.0000011.666667
2Operation_3600166.66667-0.000000
\n
" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "total_profit = pulp.value(model.objective)\n", "display(Markdown(\"Total profit is %0.2f €\"%total_profit))\n", "\n", "display(Markdown(\"The following table shows the decision variables: \"))\n", "var_df = pd.DataFrame.from_dict(cell_units, orient=\"index\", \n", " columns = [\"Variables\"])\n", "\n", "# First we add the solution. We apply a lambda function to get only two decimals:\n", "var_df[\"Solution\"] = var_df[\"Variables\"].apply(lambda item: item.varValue)\n", "# We do the same for the reduced cost:\n", "var_df[\"Reduced cost\"] = var_df[\"Variables\"].apply(lambda item: item.dj)\n", "\n", "display(var_df)\n", "\n", "\n", "const_dict = dict(model.constraints)\n", "con_df = pd.DataFrame.from_records(list(const_dict.items()), exclude=[\"Expression\"], columns=[\"Constraint\", \"Expression\"])\n", "#Now we add columns for the solution, the slack and shadow price\n", "\n", "con_df[\"Right Hand Side\"] = con_df[\"Constraint\"].apply(lambda item: -const_dict[item].constant)\n", "con_df[\"Slack\"] = con_df[\"Constraint\"].apply(lambda item: const_dict[item].slack)\n", "con_df[\"Shadow Price\"] = con_df[\"Constraint\"].apply(lambda item: const_dict[item].pi)\n", "\n", "\n", "display(Markdown(\"The following table shows the constraints: \"))\n", "display(con_df)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "pycharm": { "stem_cell": { "cell_type": "raw", "source": [], "metadata": { "collapsed": false } } } }, "nbformat": 4, "nbformat_minor": 1 }